﻿using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Collections;
using umbraco.DataLayer;
using umbraco.BusinessLogic;
using umbraco.editorControls;
using System.Collections.Generic;
using System.Data.SqlClient;
using umbraco.NodeFactory;
using System.Web.SessionState;
using umbraco.BusinessLogic.Actions;
using umbraco.cms.businesslogic.web;
using umbraco.cms.businesslogic.property;
using umbraco.interfaces;
using ExtendedWorkFlow.Helpers;
using ExtendedWorkFlow.Commands;

namespace ExtendedWorkFlow
{

    public class WorkFlow
    {

        public const string WORKFLOW_TYPE_ACTION = "WorkFlowAction";
        public const string WORKFLOW_TYPE_COMMAND = "WorkFlowCommand";
        public const string WORKFLOW_TYPE_GROUP = "WorkFlowGroup";
        public const string WORKFLOW_TYPE_GROUPS = "WorkFlowGroups";
        public const string WORKFLOW_TYPE_STATE = "WorkFlowState";

        #region Variables

        int _Id;
        int _UserId;
        DateTime _Created;
        DateTime _Updated;
        Document _Document;
        Group _Group;
        State _State;

        #region Sql Helper

        /// <summary>
        /// Current sqlhelper
        /// </summary>
        static ISqlHelper SqlHelper
        {
            get { return umbraco.BusinessLogic.Application.SqlHelper; }
        }

        #endregion

        #endregion

        #region Constructor

        /// <summary>
        /// Create new ExtendedWorkFlow
        /// </summary>
        /// <param name="Id">Id to create from</param>
        public WorkFlow(int Id)
        {
            this._Id = 0;

            try
            {
                string strSql = "select [documentid], [version], [groupid], [stateid], [userid], [createDate], [updateDate] from cmsWorkFlow where [id] = @id and [status] = 0";

                using (IRecordsReader objReader = SqlHelper.ExecuteReader(strSql, SqlHelper.CreateParameter("@id", Id)))
                {
                    if (objReader.Read())
                    {
                        Guid guidVersion = objReader.GetGuid("version");

                        // Load all objects
                        this._Document = new Document(objReader.GetInt("documentid"), guidVersion);
                        this._Group = new Group(objReader.GetInt("groupid"));
                        this._State = new State(objReader.GetInt("stateid"));
                        this._Created = objReader.GetDateTime("createDate");
                        this._Updated = objReader.GetDateTime("updateDate");
                        this._UserId = objReader.GetInt("userid");

                        this._Id = Id;
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Write(ex.ToString());
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Id of the ExtendedWorkFlow
        /// </summary>
        public int Id { get { return this._Id; } }

        /// <summary>
        /// Id of the user that created the ExtendedWorkFlow
        /// </summary>
        public int UserId { get { return this._UserId; } }

        /// <summary>
        /// DateTime the ExtendedWorkFlow was created
        /// </summary>
        public DateTime Created { get { return this._Created; } }

        /// <summary>
        /// DateTime the ExtendedWorkFlow was last updated
        /// </summary>
        public DateTime Updated { get { return this._Updated; } }

        /// <summary>
        /// Document this ExtendedWorkFlow is used by
        /// </summary>
        public Document Document { get { return this._Document; } }

        /// <summary>
        /// ExtendedWorkFlow group being executed
        /// </summary>
        public Group Group { get { return this._Group; } }

        /// <summary>
        /// Current state within the ExtendedWorkFlow
        /// </summary>
        public State State { get { return this._State; } }

        #endregion

        #region Static functions

        /// <summary>
        /// Get all ExtendedWorkFlow items currently being processed for a document
        /// </summary>
        /// <param name="DocumentId"></param>
        /// <returns></returns>
        public static WorkFlow[] GetWorkFlows(int DocumentId)
        {
            List<WorkFlow> colWorkFlow = new List<WorkFlow>();

            try
            {
                string strSql = @"
                select	id
                from	cmsWorkFlow
                where	[documentid] = @documentid and [status] = 0";

                // Load all ExtendedWorkFlows currently open for document
                using (IRecordsReader objReader = SqlHelper.ExecuteReader(strSql, new IParameter[] {
                    SqlHelper.CreateParameter("@documentid", DocumentId)}))
                {
                    while (objReader.Read())
                    {
                        int intId = objReader.GetInt("id");
                        colWorkFlow.Add(new WorkFlow(intId));
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            // return the ExtendedWorkFlows
            return colWorkFlow.ToArray();
        }

        #endregion

        #region New ExtendedWorkFlow

        /// <summary>
        /// Create new ExtendedWorkFlow item
        /// </summary>
        /// <param name="Document">Document that will have ExtendedWorkFlow</param>
        /// <param name="Group">ExtendedWorkFlow Group</param>
        /// <returns>New ExtendedWorkFlow item</returns>
        public static WorkFlow MakeNew(Document Document, Node Group)
        {
            if (WorkFlow.IsWorkFlowGroup(Group.Id, Document.Id))
            {
                // ExtendedWorkFlow already exists kiil current ExtendedWorkFlow and restart
                if (Group.Properties["restartWorkFlow"] != null &&
                    Functions.ToBool(Group.Properties["restartFlow"].Value))
                {
                    // Reset ExtendedWorkFlow
                    WorkFlow.CompleteWorkFlow(Group, Document);
                }
            }

            Guid guidId = Guid.NewGuid();
            int intInitalState = Group.Children[0].Id;

            // Add new ExtendedWorkFlow
            string strSql = "insert into cmsWorkFlow ([uid], [documentid], [version], [groupid], [stateid], [createDate], [updateDate], [userid], [status]) VALUES (@uid, @documentid, @version, @groupid, @stateid, @createDate, @updateDate, @userid, 0)";
            SqlHelper.ExecuteNonQuery(strSql, new IParameter[] {
                SqlHelper.CreateParameter("@uid", guidId), 
                SqlHelper.CreateParameter("@documentid", Document.Id),
                SqlHelper.CreateParameter("@version", Document.Version),
                SqlHelper.CreateParameter("@groupid", Group.Id),
                SqlHelper.CreateParameter("@createDate", DateTime.Now),
                SqlHelper.CreateParameter("@updateDate", DateTime.Now),
                SqlHelper.CreateParameter("@userid", Functions.ToInt(umbraco.BusinessLogic.User.GetCurrent().Id)),
                SqlHelper.CreateParameter("@stateid", intInitalState) });

            // Get the id
            int intId = SqlHelper.ExecuteScalar<int>("select [id] from cmsWorkFlow where [uid] = @uid", SqlHelper.CreateParameter("@uid", guidId));

            WorkFlow objWorkFlow = new WorkFlow(intId);


            // Make log entry
            umbraco.BusinessLogic.Log.Add(
                LogTypes.Custom,
                umbraco.helper.GetCurrentUmbracoUser(),
                objWorkFlow.Document.Id,
                "ExtendedWorkFlow started [" + objWorkFlow.Group.Name + "]");

            // Initate state commands
            objWorkFlow.DoStateCommands();

            return objWorkFlow;
        }

        /// <summary>
        /// Complete ExtendedWorkFlow for all current unstatus ExtendedWorkFlow in group
        /// </summary>
        /// <param name="GroupId">GroupId ExtendedWorkFlow assiciated with</param>
        /// <param name="DocumentId">Document to complete ExtendedWorkFlow for</param>
        /// <returns>Bool indicating if successful</returns>
        static bool CompleteWorkFlow(Node Group, Document Document)
        {
            string strSql = @"
                update cmsWorkFlow set
	                cmsWorkFlow.status = 1
                where
		                cmsWorkFlow.documentid = @documentid
	                and cmsWorkFlow.groupid = @groupid
	                and cmsWorkFlow.status = 0";

            bool blnstatus = Functions.ToBool(SqlHelper.ExecuteNonQuery(strSql, new IParameter[] {
                SqlHelper.CreateParameter("@documentid", Document.Id), 
                SqlHelper.CreateParameter("@groupid", Group.Id)}));

            if (blnstatus)
            {
                // Make log entry
                umbraco.BusinessLogic.Log.Add(
                    LogTypes.Custom,
                    umbraco.helper.GetCurrentUmbracoUser(),
                    Document.Id,
                    "WorkFlow restarted for group [" + Group.Name + "]");
            }

            return blnstatus;
        }

        /// <summary>
        /// Complete ExtendedWorkFlow for document
        /// </summary>
        /// <param name="DocumentId">Document to complete ExtendedWorkFlow for</param>
        /// <returns>Bool indicating if successful</returns>
        public static bool CompleteWorkFlow(Document Document)
        {
            string strSql = @"
                update cmsWorkFlow set
	                cmsWorkFlow.status = 1
                where
		                cmsWorkFlow.documentid = @documentid
	                and cmsWorkFlow.status = 0";

            bool blnstatus = Functions.ToBool(SqlHelper.ExecuteNonQuery(strSql, new IParameter[] {
                SqlHelper.CreateParameter("@documentid", Document.Id) }));

            if (blnstatus)
            {
                // Make log entry
                umbraco.BusinessLogic.Log.Add(
                    LogTypes.Custom,
                    umbraco.helper.GetCurrentUmbracoUser(),
                    Document.Id,
                    "All WorkFlow Complete");
            }

            return blnstatus;
        }

        /// <summary>
        /// Check if ExtendedWorkFlow for a group is already in progress
        /// </summary>
        /// <param name="GroupId">Group to check</param>
        /// <param name="DocumentId">Document to see if ExtendedWorkFlow exists for</param>
        /// <returns>Bool indicating if ExtendedWorkFlow exsits</returns>
        static bool IsWorkFlowGroup(int GroupId, int DocumentId)
        {
            string strSql = @"
                select
	                COUNT(*)
                from
	                cmsWorkFlow
                where
		                cmsWorkFlow.documentid = @documentid
	                and cmsWorkFlow.groupid = @groupid
	                and cmsWorkFlow.status = 0";

            return Functions.ToBool(SqlHelper.ExecuteScalar<int>(strSql, new IParameter[] {
                SqlHelper.CreateParameter("@documentid", DocumentId), 
                SqlHelper.CreateParameter("@groupid", GroupId)}));
        }

        #endregion

        #region Commands

        /// <summary>
        /// Perform all commands linked to this state
        /// </summary>
        public void DoStateCommands()
        {
            List<WorkFlowCommandBinding> colWorkFlowCommandBindings = new List<WorkFlowCommandBinding>();

            // Get all commands
            foreach (Node objCommand in this.State.Children)
                if (string.Compare(objCommand.NodeTypeAlias, WorkFlow.WORKFLOW_TYPE_COMMAND, true) == 0)
                {
                    // Get the assembly class within the given namespace
                    IExtendedWorkFlowCommand objWorkFlowCommandInstance = WorkFlowHelper.GetRunCommand(objCommand.Properties["type"].Value);
                    if (objWorkFlowCommandInstance != null)
                    {
                        // Create new binding for the command
                        WorkFlowCommandBinding objWorkFlowCommandBinding = new WorkFlowCommandBinding(
                            objWorkFlowCommandInstance,
                            objCommand,
                            this);

                        colWorkFlowCommandBindings.Add(objWorkFlowCommandBinding);
                    }
                    else
                    {
                        // Make log entry
                        umbraco.BusinessLogic.Log.Add(
                            LogTypes.Custom,
                            umbraco.helper.GetCurrentUmbracoUser(),
                            this.Document.Id,
                            "Command failed to load assembly [" + objCommand.Name + "]");
                    }
                }

            // Action all commands
            foreach (WorkFlowCommandBinding objWorkFlowCommandBinding in colWorkFlowCommandBindings)
            {
                // Do action of the command in the binding
                bool blnAction = objWorkFlowCommandBinding.DoAction();

                // Make log entry
                umbraco.BusinessLogic.Log.Add(
                    LogTypes.Custom,
                    umbraco.helper.GetCurrentUmbracoUser(),
                    this.Document.Id,
                    "Command Actioned [" + objWorkFlowCommandBinding.Command.Name + "] " + (blnAction ? "successfully" : "but failed"));
            }
        }

        #endregion

        #region Actions

        /// <summary>
        /// Check if a node has the current user set into ExtendedWorkFlow table by either role or id
        /// </summary>
        /// <param name="NodeId">NodeId to lookup</param>        
        /// <returns>Bool indicating if users has permissions to nodeid</returns>
        public bool IsUserWorkFlow(int DocumentId)
        {
            return this.IsUserWorkFlow(DocumentId, umbraco.BusinessLogic.User.GetCurrent());
        }

        /// <summary>
        /// Check if a node has the user set into ExtendedWorkFlow table by either role or id
        /// </summary>
        /// <param name="NodeId">NodeId to lookup</param>
        /// <param name="User">User to check against</param>
        /// <returns>Bool indicating if users has permissions to nodeid</returns>
        public bool IsUserWorkFlow(int DocumentId, umbraco.BusinessLogic.User User)
        {
            string strSql = @"
                select   
                    Count(*)
                from
                    umbracoUser
                    CROSS JOIN
                    cmsDocument 
                    inner join cmsWorkFlowUserRoles StatePermissions on 
                            StatePermissions.nodeId = cmsDocument.nodeId
                        and	(
		                            StatePermissions.id = umbracoUser.id and StatePermissions.type = 'u'
	                            OR	StatePermissions.id = umbracoUser.userType and StatePermissions.type = 't'
                            )
                where
                        umbracouser.id = @id
                    and cmsDocument.newest = 1
                    and cmsDocument.nodeId = @documentid";

            return Functions.ToBool(SqlHelper.ExecuteScalar<int>(strSql, new IParameter[] {
                SqlHelper.CreateParameter("@id", User.Id), 
                SqlHelper.CreateParameter("@documentid", DocumentId)}));
        }

        /// <summary>
        /// Move to next state
        /// </summary>
        public bool NextState()
        {
            // Get next state 
            if (this.State != null &&
                this.State.Id != 0)
            {
                List<INode> colStates = this.Group.ChildrenAsList;
                int intPos = 0;

                // Find the state index
                for (int intIndex = 0; intIndex < colStates.Count; intIndex++)
                    if (colStates[intIndex].Name == this.State.Name)
                        intPos = intIndex;

                if (intPos < (colStates.Count - 1))
                {
                    // Next state node
                    State objState = new State(colStates[intPos + 1].Id);
                    return NextState(objState);
                }
            }

            return false;
        }

        /// <summary>
        /// Check if a state is the end of WorkFlow group
        /// </summary>
        /// <param name="State">State to check</param>
        /// <returns>bool inidicating if a state is the end of the ExtendedWorkFlow</returns>
        public bool IsStateEnd(State State)
        {
            // Get next state 
            if (this.State != null &&
                this.State.Id != 0)
            {
                List<INode> colStates = this.Group.ChildrenAsList;

                // Check state is either the end state or its explicitly set as the end of
                // the ExtendedWorkFlow process
                if (colStates[colStates.Count - 1].Id == State.Id ||
                    State.IsCompleted)
                    return true;
                else
                    return false;
            }

            return false;
        }

        /// <summary>
        /// Change the state of this ExtendedWorkFlow
        /// </summary>
        /// <param name="State">State to move to</param>
        public bool NextState(State State)
        {
            return this.NextState(State, true);
        }

        /// <summary>
        /// Change the state of this ExtendedWorkFlow
        /// </summary>
        /// <param name="State">State to move to</param>
        /// <param name="CheckPermissions">Check if the current user have permission to goto this state</param>
        public bool NextState(State State, bool CheckPermissions)
        {
            if (State != null &&
                State.Id != 0 &&
                State.NodeTypeAlias == WorkFlow.WORKFLOW_TYPE_STATE)
            {
                // Check the user has valid permissions to update the state
                if (this.IsUserWorkFlow(this.State.Id) ||
                    CheckPermissions == false)
                {

                    string strSql = "update cmsWorkFlow set [stateid] = @stateid, [status] = @status, [updateDate] = @updateDate where id = @id";
                    SqlHelper.ExecuteNonQuery(strSql, new IParameter[] {
                        SqlHelper.CreateParameter("@stateid", State.Id), 
                        SqlHelper.CreateParameter("@id", this.Id),
                        SqlHelper.CreateParameter("@status", this.IsStateEnd(State)),
                        SqlHelper.CreateParameter("@updateDate", DateTime.Now) });

                    this._State = State;
                    this.DoStateCommands();

                    // Make log entry
                    umbraco.BusinessLogic.Log.Add(
                        LogTypes.Custom,
                        umbraco.helper.GetCurrentUmbracoUser(),
                        this.Document.Id,
                        "WorkFlow state changed to [" + this._State.Name + "]");

                    return true;
                }
            }

            return false;
        }

        #endregion

    }

}
